package server.services;

import java.util.HashMap;

import comandos.ProtocoloTateti;

import model.Usuario;
import server.exception.ServiceException;
import server.tateti.Jugador;
import server.tateti.Tateti;

/**
 * Esta clase administra todos los juegos de tateti que estan siendo ejecutados
 */

public class TatetiService {
	private static TatetiService instance;

	private HashMap<Integer, Tateti> partidas;
	private ProtocoloTateti protocolo;
	private int proxID;

	/**
	 * Constructor privado. Obtener instancia con getInstance()
	 */
	private TatetiService() {
		proxID = Integer.MIN_VALUE;
		partidas = new HashMap<>();
	}

	/**
	 * Establece el protocolo de comunicaciones
	 * 
	 * @param protocolo
	 */
	public void setProtocolo(ProtocoloTateti protocolo) {
		this.protocolo = protocolo;
	}

	/**
	 * Crea una partida nueva de tateti
	 * 
	 * @param invitador
	 *            Usuario que inicia la partida
	 * @param invitado
	 *            Usuario que sera invitado a jugar
	 * @return true en caso de exito, false en caso contrario
	 * @throws ServiceException
	 *             En caso de que no se pueda crear la partida
	 */
	public synchronized boolean crearPartida(Usuario invitador, Usuario invitado)
			throws ServiceException {
		try {
			// Creamos la partida
			Tateti tateti = new Tateti(proxID++, invitador, invitado);
			// Invitamos al otro jugador
			protocolo.invitar(invitado, tateti.getId(), invitador);
			// Almacenamos la partida
			partidas.put(tateti.getId(), tateti);
			
			System.out.println("Tateti " + tateti.getId() + ": "
					+ invitador.getNick() + " invito a jugar a " + invitado.getNick());
			return true;
		} catch (Exception e) {
			throw new ServiceException("No se pudo crear la partida", e);
		}
	}

	/**
	 * Marca en el tateti y verifica si hubo ganadores
	 * 
	 * @param id
	 *            Identificador de partida
	 * @param usuario
	 *            Usuario que desea marcar en el tateti
	 * @param posicion
	 *            Posicion que desea marcar
	 * @throws ServiceException
	 *             En caso que ocurra un error durante el proceso
	 */
	public void marcar(int id, Usuario usuario, int posicion)
			throws ServiceException {
		// Obtengo partida
		Tateti tateti = partidas.get(id);
		try {
			// Marco la posicion requerida
			Jugador jugador = tateti.getJugador(usuario);
			if (jugador != null && tateti.marcar(jugador.getId(), posicion)) {
				// Notifico a los jugadores el cambio
				for (Jugador j : tateti.getJugadores())
					protocolo.posicionMarcada(j.getUsuario(), id, jugador,
							posicion);
			}
			System.out.println("Tateti " + tateti.getId() + ": "
					+ usuario.getNick() + " marco en la posicion" + posicion);
		} catch (Exception e) {
			throw new ServiceException("No se pudo marcar", e);
		}
		if (tateti != null)
			verificarGanador(tateti);
	}

	/**
	 * Notifica el abandono de alguno de los jugadores del tateti
	 * 
	 * @param id
	 *            Identificador de partida
	 * @param usuario
	 *            Usuario desertor
	 * @throws ServiceException
	 *             En caso que ocurra un error durante el proceso
	 */
	public void abandonar(int id, Usuario usuario) throws ServiceException {
		try {
			// Obtengo partida y el jugador
			Tateti tateti = partidas.get(id);
			Jugador desertor = tateti.getJugador(usuario);
			if (desertor != null) {
				int idAbandonado = desertor.getId();
				int idOtroJugador = idAbandonado == Tateti.J1 ? Tateti.J2
						: Tateti.J1;
				Jugador elOtroJugador = tateti.getJugador(idOtroJugador);
				// Notifico el abandono
				protocolo.abandono(elOtroJugador.getUsuario(), id, desertor);
				System.out.println("Tateti " + tateti.getId() + ": "
						+ usuario.getNick() + " abandono la partida");
			}
		} catch (Exception e) {
			throw new ServiceException("No se pudo crear la partida", e);
		}
	}

	/**
	 * Se notifica la respuesta de una invitacion para jugar
	 * 
	 * @param id
	 *            Identificador de partida
	 * @param usuario
	 *            Usuario que notifica su respuesta
	 * @param respuesta
	 *            Respuesta propiamente dicha
	 * @throws ServiceException
	 *             En caso que ocurra un error durante el proceso
	 */
	public void responderInvitacion(int id, Usuario usuario, boolean respuesta)
			throws ServiceException {
		try {
			// Obtengo partida
			Tateti tateti = partidas.get(id);
			// Verifico la respuesta y que el jugador pertenezca a ella
			if (respuesta) {
				// Marco al jugador como listo para jugar
				tateti.getJugadores()[Tateti.J2].setListo(true);
				System.out.println("Tateti " + tateti.getId() + ": "
						+ usuario.getNick() + " acepto la invitacion");
				iniciarPartida(tateti);
			} else 
			System.out.println("Tateti " + tateti.getId() + ": "
					+ usuario.getNick() + " no acepto la invitacion");
		} catch (Exception e) {
			throw new ServiceException("No se pudo iniciar el tateti", e);
		}
	}

	/**
	 * Notifica que un usuario quiere una revancha luego de terminar una partida
	 * 
	 * @param id
	 *            Identificador de partida
	 * @param usuario
	 *            Usuario que quiere la revancha
	 * @throws ServiceException
	 *             En caso de que no se pueda concretar la accion
	 */
	public void revancha(int id, Usuario usuario) throws ServiceException {
		try {
			// Obtengo partida y el jugador
			Tateti tateti = partidas.get(id);
			Jugador jugador = tateti.getJugador(usuario);

			// Verifico que el jugador pertenezca a ella
			if (jugador != null) {
				// Marco al jugador como listo para jugar
				jugador.setListo(true);
			}
			System.out.println("Tateti " + tateti.getId() + ": "
					+ usuario.getNick() + " quiere revancha");
			
			if(tateti.getJugador(Tateti.J1).isListo() && tateti.getJugador(Tateti.J1).isListo()) {
				System.out.println("Tateti " + tateti.getId() + ": "
						+ usuario.getNick() + " iniciando revancha");
				tateti.inicializar();
				this.iniciarPartida(tateti);
			}
		} catch (Exception e) {
			throw new ServiceException(
					"No se pudo anotar el pedido de revancha", e);
		}
	}

	/**
	 * Verifica si hay ganadores, en caso de haberlos, notifica el ganador
	 * 
	 * @param tateti
	 * @throws ServiceException
	 */
	private void verificarGanador(Tateti tateti) throws ServiceException {
		try {
			int idGanador = tateti.verificarGanador();
			if (idGanador != Tateti.NINGUN_JUGADOR) {
				for (Jugador j : tateti.getJugadores())
					protocolo.notificarGanador(j.getUsuario(), tateti.getId(),
							idGanador);
				if(idGanador != Tateti.EMPATE) {
					Jugador ganador = tateti.getJugador(idGanador);
					System.out.println("Tateti " + tateti.getId() + ": "
						+ ganador.getUsuario().getNick()
						+ " es el ganador");
				} else {
					System.out.println("Tateti " + tateti.getId() + ": "
							+ " empate");
				}
			}
			
		} catch (Exception e) {
			throw new ServiceException("No se pudo notificar el ganador", e);
		}
	}
	
	private void iniciarPartida(Tateti tateti) throws Exception {
		System.out.println("Tateti " + tateti.getId() + ": Iniciando partida");
		tateti.inicializar();
		// Notifico a los jugadores el inicio de la partida
		for (Jugador j : tateti.getJugadores())
			protocolo.iniciarTateti(j.getUsuario(), tateti);
	}

	/**
	 * Obtiene una instancia del service. Patron Singleton
	 * 
	 * @return Instancia del servicio
	 */
	public static TatetiService getInstance() {
		if (instance == null)
			instance = new TatetiService();
		return instance;
	}

}
